#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>

#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " "
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44


int lastcode=0;
int quit=0;

//extern char** environ;
char commandline[LINE_SIZE];
char* argv[ARGC_SIZE];

char pwd[LINE_SIZE];
//自定义环境变量表
char myenv[LINE_SIZE];
//自定义本地变量表
//...   

const char* getusername()
{
    return getenv("USER");
}

const char* gethostname()
{
    return getenv("HOSTNAME");
}

void getpwd()
{
    getcwd(pwd,sizeof(pwd));
}

//交互
void interact(char *cline, int size)
{
    getpwd();
    printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),gethostname(),pwd);
    char *s = fgets(cline,size,stdin);
    //s一定会读成功，直接回车也有字符输入
    assert(s != NULL);
    (void)s;//用一下s,防止报警告

    //“abcd\n\0"把回车置零"
    cline[strlen(cline)-1] = '\0';
}

//分割字符
int splitstring(char cline[],char * _argv[])
{
    int i=0;
    //commandline -> "ls -a -l -n\0" ->"ls" "-a" "-l" "-n"
    //strtok
    _argv[i++] = strtok(cline,DELIM);
    while(_argv[i++] = strtok(NULL,DELIM));//此处没有错误，编译器识别不清,故意写的=
    return i-1;
}

//正常执行
void NormalExcute(char* _argv[])
{

    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return;
    }
    else if( id == 0 )
    {
        //让子进程执行命令
//        execvpe(_argv[0],_argv,environ);
        execvp(_argv[0],_argv);
        //只要返回，替换必定出错
        exit(EXIT_CODE);
        //子进程执行不影响父进程
        //即内建命令不能由子进程执行，如cd
    }
    else
    {
        int status =0;
        pid_t rid = waitpid(id,&status,0);
        if(rid == id)
        {
            //等待子进程成功
            lastcode = WEXITSTATUS(status);
        }
    }
}

int buildCommand(char* _argv[], int _argc)
{
    //内建命令cd
    if(_argc ==2 && strcmp(_argv[0],"cd")==0)
    {
        //chdir修改工作路径
        chdir(_argv[1]);
        getpwd();
        //格式化到
        sprintf(getenv("PWD"),"%s",pwd);
        return 1;
    }
    //内建命令export
    else if(_argc == 2 && strcmp(_argv[0], "export") == 0)
    {
        strcpy(myenv, _argv[1]);
        putenv(myenv);
        //ps 导环境变量，是把这个数组的地址放到环境变量的表里
        //不是把数组的内容放在环境变量表里
        //当_argv[1]发生改变，环境变量表内指向的内容也改变了
        return 1;
    }
    //内建命令echo
    else if(_argc == 2 && strcmp(_argv[0], "echo")== 0)
    {
        if(strcmp(_argv[1],"$?") == 0)
        {
            printf("%d\n",lastcode);
            lastcode=0;
        }
        else if(*_argv[1] == '$')//_argv[1] $PATH    故*_argv[1] $
        {
            char *val = getenv(_argv[1]+1);
            if(val) printf("%s\n",getenv(_argv[1]+1));
        }
        else
        {
            printf("%s\n",_argv[1]);
        }
        return 1;
    }



    //ls增加颜色
    if(strcmp(_argv[0],"ls") == 0)
    {
        _argv[_argc++] = "--color";
        _argv[_argc]=NULL;
    }
    
    return 0;
}

int main()
{

    //循环执行shell
    while(!quit)
    {
        //1.
        //2.交互问题，获取命令行
         interact(commandline, sizeof(commandline));
         

         //3.子串切割的问题，解析命令行
         int argc = splitstring(commandline,argv);
         //返回有几组字符串
         if(argc == 0) continue;

         //4.指令的判断
         //调用系统接口方案，还可以靠修改环境变量
         //
         //内建命令，本质就是一个shell内部的一个函数
         int n =  buildCommand(argv,argc);
    

         //5.普通命令的执行,创建子进程
         if(!n) NormalExcute(argv);
    }
    return 0;
}
